package mybatis.utils;


import mybatis.annotations.Select;
import mybatis.cfg.Configuration;
import mybatis.cfg.Mapper;
import mybatis.io.Resources;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 解析配置文件 dom4j+xpath
 */
public class XmlConfigBuilder {

    public static Configuration loadConfiguration(InputStream inputStream){
        try {
            //定义封装连接信息的配置对象（mybatis的配置对象）
            Configuration configuration = new Configuration();
            //1.获取SAXReader对象
            SAXReader saxReader = new SAXReader();
            //2.根据字节输入流获取Document对象
            Document document = saxReader.read(inputStream);
            //3.获取根节点
            Element rootElement = document.getRootElement();
            //4.使用xpath中选择指定节点的方式，获取所有property节点
            List<Element> propertyElements = rootElement.selectNodes("//property");
            for (Element propertyElement : propertyElements) {
                //判断节点是连接数据库的哪部分信息
                //取出name属性的值
                String name = propertyElement.attributeValue("name");
                //取出value属性的值
                String value = propertyElement.attributeValue("value");
                switch (name){
                    case "driver":
                        configuration.setDriver(value);
                        break;
                    case "url":
                        configuration.setUrl(value);
                        break;
                    case "username":
                        configuration.setUsername(value);
                        break;
                    case "password":
                        configuration.setPassword(value);
                        break;
                }
            }
            //取出mappers中的所有mapper标签，判断他们使用了resource还是class属性
            List<Element> mapperElements = rootElement.selectNodes("//mappers/mapper");
            for (Element mapperElement : mapperElements) {
                final Attribute attribute = mapperElement.attribute("resource");
                if(attribute != null){
                    //表示有resource属性，用的是XML
                    //取出属性的值 //获取属性的值"dao/IUserDao.xml"
                    String mapperPath = attribute.getValue();
                    //把映射配置文件的内容获取出来，封装成一个map
                    Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                    configuration.setMappers(mappers);
                }else{
                    //表示没有resource属性，用的是注解
                    //获取class属性的值
                    String daoPath = mapperElement.attributeValue("class");
                    //根据daoClassPath获取封装的必要信息
                    Map<String,Mapper> mappers = loadMapperAnnotation(daoPath);
                    configuration.setMappers(mappers);
                }
            }
            return configuration;
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static Map<String, Mapper> loadMapperAnnotation(String daoPath) {
        Map<String,Mapper> mappers = new HashMap<>();
        try {
            //得到dao接口的字节码对象
            Class<?> daoInterfaceClass = Class.forName(daoPath);
            //得到dao接口中的方法数组
            Method[] methods = daoInterfaceClass.getMethods();
            //遍历Method数组
            for (Method method : methods) {
                //判断是否有select注解
                boolean annotationPresent = method.isAnnotationPresent(Select.class);
                if(annotationPresent){
                    Mapper mapper = new Mapper();
                    //取出注解的value属性值
                    Select select = method.getAnnotation(Select.class);
                    final String queryString = select.value();
                    mapper.setQueryString(queryString);
                    //获取当前方法的返回值，带有泛型信息  List<User>
                    Type type = method.getGenericReturnType();
                    //判断type是不是参数化的类型
                    if(type instanceof ParameterizedType){
                        ParameterizedType ptype = (ParameterizedType)type;
                        //得到参数化类型中的实际类型参数
                        Type[] arguments = ptype.getActualTypeArguments();
                        final Class domainClass = (Class)arguments[0];
                        //获取domainClass的类名
                        String resultType = domainClass.getName();
                        mapper.setResultType(resultType);
                    }
                    //namespace:类名+方法名
                    String key = method.getDeclaringClass().getName()+"."+method.getName();
                    mappers.put(key,mapper);
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return mappers;
    }

    /**
     *
     * @param mapperPath 映射配置文件的位置
     * @return  map中包含了获取的唯一标识（key是由dao的全限定类名和方法名组成）
     *          以及执行所需的必要信息（value是一个Mapper对象，里面存放的是执行的SQL语句和要封装的实体类全限定类名）
     */
    private static Map<String, Mapper> loadMapperConfiguration(String mapperPath) {
        InputStream inputStream = null;
        try {
            //定义返回值类型
            Map<String,Mapper> mappers = new HashMap<>();
            //根据路径获取字节输入流
            inputStream = Resources.getResourceAsStream(mapperPath);
            //获取SAXReader对象
            SAXReader saxReader = new SAXReader();
            //根据字节输入流获取Document对象
            Document document = saxReader.read(inputStream);
            //获取根结点
            Element rootElement = document.getRootElement();
            //获取namespace的值
            String namespace = rootElement.attributeValue("namespace");
            //获取所有的select节点
            List<Element> selectElements = rootElement.selectNodes("//select");
            for (Element selectElement : selectElements) {
                //取出id的值
                String id = selectElement.attributeValue("id");
                //取出resultType
                String resultType = selectElement.attributeValue("resultType");
                //取出文本内容
                String queryString = selectElement.getText();
                //key
                String key = namespace+"."+id;
                //value
                Mapper mapper = new Mapper();
                mapper.setQueryString(queryString);
                mapper.setResultType(resultType);
                mappers.put(key,mapper);
            }
            return mappers;
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
